﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.Contracts;
using System.Linq;
using Caliburn.Micro;
using CardPlay.Client.Model;
using CardPlay.Model;
using CardPlay.Model.GameModel;
using CardPlay.Model.GameModel.Actions;
using CardPlay.Model.GameModel.Presentation;
using CardPlay.Model.Layout;
using CardPlay.Presentation;

namespace CardPlay.Client.ViewModels
{
    public class GameViewModel : ITreeNode, IServiceProvider, IPresentationContext, IPlayingContext
    {
        private readonly RecordOfPlay _recordOfPlay;
        private readonly Stopwatch _stopwatch = new Stopwatch();
        private readonly GameCanvas _canvas;
        private ICardGame _game;

        public static GameViewModel Create(IGameDescriptor provider, IRandomNumberSequence series)
        {
            Contract.Requires<ArgumentNullException>(provider != null, "provider");

            var game = provider.CreateInstance();
            game.Deal(series);

            return new GameViewModel(game, provider, series.Seed);
        }

        private GameViewModel(ICardGame game, IGameDescriptor provider, int seed)
        {
            _canvas = new GameCanvas(IoC.Get<IGraphics>());
            _recordOfPlay = new RecordOfPlay
            {
                Id = Guid.NewGuid(),
                GameId = provider.Id,
                GameNumber = seed,
                Outcome = GameOutcome.Abandoned,
                TimeStarted = DateTime.Now
            };

            Descriptor = provider;
            Game = game;

            Game.Selection.CollectionChanged += OnSelectionChanged;
            Game.StateOfPlayChanged += (sender, args) => Stopwatch.IsRunning = args.NewState == StateOfPlay.InProgress;
            Game.ActionExecuted += delegate
            {
                _canvas.UpdateCues();
                _canvas.UpdateLayout();
            };
        }

        private void SelectCardsAt(IEnumerable<PartLocation> setOfLocations, bool select)
        {
            var list =
                from location in setOfLocations 
                join card in _canvas.Cards on location equals card.PartLocation
                select card;

            foreach (var card in list)
                card.IsSelected = select;
        }

        private void OnSelectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    SelectCardsAt(e.NewItems.Cast<PartLocation>(), true);
                    break;

                case NotifyCollectionChangedAction.Remove:
                    SelectCardsAt(e.OldItems.Cast<PartLocation>(), false);
                    break;

                case NotifyCollectionChangedAction.Reset:
                    foreach (var card in _canvas.Cards)
                        card.IsSelected = false;
                    break;
            }
        }

        public IGameDescriptor Descriptor
        {
            get; private set;
        }

        public string DisplayName
        {
            get { return Descriptor.DisplayName; }
        }

        public Stopwatch Stopwatch
        {
            get { return _stopwatch; }
        }

        public ICardGame Game
        {
            get { return _game; }
            private set
            {
                if (_game != null)
                    _game.Host = null;

                _game = value;

                if (_game != null)
                {
                    _game.Host = this;
                    CreateLayout();
                }
            }
        }

        public object Piles
        {
            get { return _canvas.Piles;  }   
        }

        #region ITreeNode

        public ITreeNode ParentNode
        {
            get; set;
        }

        public IEnumerable<ITreeNode> ChildNodes
        {
            get { return _canvas.Piles; }
        }

        #endregion

        public void RecordResult()
        {
            switch (_game.StateOfPlay)
            {
                case StateOfPlay.GameOver:
                    _recordOfPlay.Outcome = GameOutcome.Lost;
                    break;

                case StateOfPlay.GameComplete:
                    _recordOfPlay.Outcome = GameOutcome.Won;
                    break;

                default:
                    _recordOfPlay.Outcome = GameOutcome.Abandoned;
                    break;
            }

            _recordOfPlay.TotalMoves = _game.History.TotalChanges;
            _recordOfPlay.Duration = DateTime.Now - _recordOfPlay.TimeStarted;

#if ! SILVERLIGHT            
            GameRepository.History.AugmentHistory(_recordOfPlay);
#endif
        }

        #region IServiceProvider

        object IServiceProvider.GetService(Type serviceType)
        {
            return serviceType == typeof(IPlayingContext) ? this : null;
        }

        #endregion

        #region IPlayingContext

        public ICanvas Canvas
        {
            get { return _canvas; }
        }

        bool IPlayingContext.InvokeActionAt(PartLocation location)
        {
            return Game.ExecuteCommandAt(location, false);
        }

        void IPlayingContext.InvokeAutoActionAt(PartLocation location)
        {
            Game.ExecuteCommandAt(location, true);
        }

        IDraggable IPlayingContext.CreateDragObject(PartLocation partLocation)
        {
            var action = Game.CreatePickupActionAt(partLocation);
            return action != null && Game.Regulations.Permit(action) ? _canvas.CreateDragPile(this, action) : null;
        }

        IDisposable IPlayingContext.CreatePeekObject(PartLocation partLocation)
        {
            var action = Game.CreatePeekAction(partLocation);
            return action != null && Game.Regulations.Permit(action) ? _canvas.CreatePeekCard(this, action) : null;
        }

        bool IPlayingContext.HandleDrop(RemovePileAction pickupAction, IEnumerable<PartLocation> candidateLocations)
        {
            var move = candidateLocations
                .Select(location => Game.CreateMovePileAction(pickupAction, location))
                .FirstOrDefault(action => Game.Regulations.Permit(action));

            if (move == null) return false;

            Game.Execute(move);
            return true;
        }

        #endregion

        #region IPresentationContext

        LayoutMetrics IPresentationContext.Metrics
        {
            get { return _canvas.Metrics; }
        }

        ILayoutSite IPresentationContext.CreatePileVisual(string name, VisualTraits traits)
        {
            var pile = new PileViewModel(this, traits, _game.PileAt(new PartLocation(name))) { Name = name};

            _canvas.Add(pile);
            return pile;
        }

        #endregion

        private void CreateLayout()
        {
            _canvas.Clear();
            _canvas.Layout = Game.CreatePresentation(this);
        }
    }
}